/*
 * Copyright 2000-2006 JetBrains s.r.o.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.intellij.gwt.refactorings;

import com.intellij.gwt.GwtConfiguration;
import com.intellij.gwt.i18n.GwtI18nManager;
import com.intellij.gwt.inspections.RemoteServiceUtil;
import com.intellij.openapi.components.ProjectComponent;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.Ref;
import com.intellij.openapi.util.Comparing;
import com.intellij.psi.PsiClass;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiMethod;
import com.intellij.psi.PsiNamedElement;
import com.intellij.refactoring.RefactoringFactory;
import com.intellij.refactoring.RenameRefactoring;
import com.intellij.refactoring.listeners.RefactoringElementListener;
import com.intellij.refactoring.listeners.RefactoringElementListenerProvider;
import com.intellij.refactoring.listeners.RefactoringListenerManager;
import com.intellij.usageView.UsageInfo;
import com.intellij.lang.properties.psi.Property;
import com.intellij.lang.properties.psi.PropertiesFile;
import com.intellij.util.Function;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

import java.util.Map;
import java.util.HashMap;

public class GwtRefacroringListenerProvider implements ProjectComponent, RefactoringElementListenerProvider {
  private Project myProject;
  private static final Function<String, String> STRING2STRING_ID = new Function<String, String>() {
    public String fun(final String s) {
      return s;
    }
  };
  private ThreadLocal<Boolean> myInsideGwtListener = new ThreadLocal<Boolean>() {
    protected Boolean initialValue() {
      return Boolean.FALSE;
    }
  };

  public GwtRefacroringListenerProvider(Project project) {
    myProject = project;
  }

  public void initComponent() {
  }

  public void disposeComponent() {
  }

  @NotNull
  public String getComponentName() {
    return "GwtRefacroringListenerProvider";
  }

  public void projectOpened() {
    RefactoringListenerManager.getInstance(myProject).addListenerProvider(this);
  }

  public void projectClosed() {
    RefactoringListenerManager.getInstance(myProject).removeListenerProvider(this);
  }

  public RefactoringElementListener getListener(PsiElement element) {
    GwtConfiguration gwtConfig = GwtConfiguration.getInstance();
    if (!gwtConfig.isValidConfiguration()) return null;

    RefactoringElementListener listener = getServiceClassListener(element);
    if (listener != null) {
      return listener;
    }

    return getPropertiesClassListener(element);
  }

  @Nullable
  private RefactoringElementListener getPropertiesClassListener(PsiElement element) {
    final GwtI18nManager i18nManager = GwtI18nManager.getInstance(myProject);
    final Map<PsiNamedElement, Function<String, String>> elementsToRename = new HashMap<PsiNamedElement, Function<String, String>>(1);

    if (element instanceof Property) {
      final Property property = (Property)element;
      final PsiMethod method = i18nManager.getMethod(property);
      if (method != null && Comparing.equal(property.getKey(), method.getName())) {
        elementsToRename.put(method, STRING2STRING_ID);
      }
    }

    if (element instanceof PsiMethod) {
      final PsiMethod method = (PsiMethod)element;
      final Property[] properties = i18nManager.getProperties(method);
      for (Property property : properties) {
        if (Comparing.equal(property.getKey(), method.getName())) {
          elementsToRename.put(property, STRING2STRING_ID);
        }
      }
    }

    if (element instanceof PsiClass) {
      final PsiClass psiClass = (PsiClass)element;
      final String className = psiClass.getName();
      final PropertiesFile[] files = i18nManager.getPropertiesFiles(psiClass);
      for (PropertiesFile file : files) {
        final String fileName = file.getName();
        if (fileName != null && className != null && fileName.startsWith(className)) {
          final String suffix = fileName.substring(className.length());
          elementsToRename.put(file, new Function<String, String>() {
            public String fun(final String s) {
              return s + suffix;
            }
          });
        }
      }
    }

    if (!elementsToRename.isEmpty()) {
      return new RefactoringElementListenerBase() {
        public void elementRenamed(PsiElement newElement) {
          for (Map.Entry<PsiNamedElement, Function<String, String>> entry : elementsToRename.entrySet()) {
            final PsiNamedElement psiElement = entry.getKey();
            final String newName = ((PsiNamedElement)newElement).getName();
            rename(psiElement, entry.getValue().fun(newName));
          }
        }
      };
    }
    return null;
  }

  @Nullable
  private RefactoringElementListener getServiceClassListener(final PsiElement element) {
    if (!(element instanceof PsiClass)) return null;

    final PsiClass psiClass = (PsiClass)element;
    if (!RemoteServiceUtil.isRemoteServiceInterface(psiClass)) {
      return null;
    }

    final PsiClass async = RemoteServiceUtil.findAsynchronousInterface(psiClass);

    if (async == null) {
      return null;
    }

    return new RefactoringElementListenerBase() {
      public void elementRenamed(PsiElement newElement) {
        rename(async, ((PsiClass)newElement).getName() + RemoteServiceUtil.ASYNC_SUFFIX);
      }
    };
  }

  private void rename(final PsiElement element, final String newName) {
    if (Boolean.TRUE.equals(myInsideGwtListener.get())) {
      return;
    }
    try {
      myInsideGwtListener.set(true);
      RenameRefactoring rename = RefactoringFactory.getInstance(myProject).createRename(element, newName);
      rename.setSearchInComments(false);
      rename.setSearchInNonJavaFiles(false);
      rename.setShouldRenameVariables(false);
      rename.setShouldRenameInheritors(false);
      rename.setPreviewUsages(false);

      UsageInfo[] usage = rename.findUsages();
      if (rename.preprocessUsages(new Ref<UsageInfo[]>(usage))) rename.doRefactoring(usage);
    }
    finally{
      myInsideGwtListener.set(false);
    }
  }

  private static abstract class RefactoringElementListenerBase implements RefactoringElementListener {
    public void elementMoved(PsiElement newElement) {
    }

    public void elementRenamed(PsiElement newElement) {
    }
  }
}
